//
// Source code recreated from a .class file by IntelliJ IDEA
// (powered by Fernflower decompiler)
//
package com.amazonaws.services.s3.transfer

import com.amazonaws.AmazonClientException
import com.amazonaws.AmazonServiceException
import com.amazonaws.AmazonWebServiceRequest
import com.amazonaws.annotation.SdkInternalApi
import com.amazonaws.auth.AWSCredentials
import com.amazonaws.auth.AWSCredentialsProvider
import com.amazonaws.auth.DefaultAWSCredentialsProviderChain
import com.amazonaws.event.ProgressListener
import com.amazonaws.event.ProgressListenerChain
import com.amazonaws.services.s3.AmazonS3
import com.amazonaws.services.s3.AmazonS3Client
import com.amazonaws.services.s3.internal.FileLocks
import com.amazonaws.services.s3.internal.Mimetypes
import com.amazonaws.services.s3.internal.RequestCopyUtils
import com.amazonaws.services.s3.internal.ServiceUtils
import com.amazonaws.services.s3.model.*
import com.amazonaws.services.s3.transfer.*
import com.amazonaws.services.s3.transfer.Transfer.TransferState
import com.amazonaws.services.s3.transfer.exception.FileLockException
import com.amazonaws.services.s3.transfer.internal.*
import com.amazonaws.util.VersionInfoUtils
import com.amazonaws.services.s3.transfer.internal.MyUploadImpl
import org.slf4j.Logger
import org.slf4j.LoggerFactory
import java.io.File
import java.io.IOException
import java.io.InputStream
import java.util.*
import java.util.concurrent.*
import java.util.concurrent.atomic.AtomicInteger
import kotlin.math.min

class MyTransferManager : TransferManager {
    private val s3: AmazonS3
    private var configuration: TransferManagerConfiguration
    private val executorService: ExecutorService
    private val timedThreadPool: ScheduledExecutorService
    private val shutDownThreadPools: Boolean
    private val isImmutable: Boolean

    @Deprecated("")
    constructor() : this(AmazonS3Client(DefaultAWSCredentialsProviderChain()) as AmazonS3)


    @Deprecated("")
    constructor(credentialsProvider: AWSCredentialsProvider?) : this(AmazonS3Client(credentialsProvider) as AmazonS3)


    @Deprecated("")
    constructor(credentials: AWSCredentials?) : this(AmazonS3Client(credentials) as AmazonS3)


    @Deprecated("")
    constructor(s3: AmazonS3) : this(s3, TransferManagerUtils.createDefaultExecutorService())


    @Deprecated("")
    constructor(s3: AmazonS3, executorService: ExecutorService) : this(s3, executorService, true)


    @Deprecated("")
    constructor(s3: AmazonS3, executorService: ExecutorService, shutDownThreadPools: Boolean) {
        this.timedThreadPool = ScheduledThreadPoolExecutor(1, daemonThreadFactory)
        this.s3 = s3
        this.executorService = executorService
        this.configuration = TransferManagerConfiguration()
        this.shutDownThreadPools = shutDownThreadPools
        this.isImmutable = false
    }

    @SdkInternalApi
    internal constructor(params: TransferManagerParams) {
        this.timedThreadPool = ScheduledThreadPoolExecutor(1, daemonThreadFactory)
        this.s3 = params.s3Client
        this.executorService = params.executorService
        this.configuration = params.configuration
        this.shutDownThreadPools = params.shutDownThreadPools
        this.isImmutable = true
    }

    protected constructor(builder: TransferManagerBuilder) : this(builder.params)


    @Deprecated("")
    override fun setConfiguration(configuration: TransferManagerConfiguration) {
        this.checkMutability()
        this.configuration = configuration
    }

    override fun getConfiguration(): TransferManagerConfiguration {
        return this.configuration
    }

    override fun getAmazonS3Client(): AmazonS3 {
        return this.s3
    }

    @Throws(AmazonServiceException::class, AmazonClientException::class)
    override fun upload(
        bucketName: String,
        key: String,
        input: InputStream,
        objectMetadata: ObjectMetadata
    ): MyUploadImpl {
        return this.upload(PutObjectRequest(bucketName, key, input, objectMetadata))
    }

    @Throws(AmazonServiceException::class, AmazonClientException::class)
    override fun upload(bucketName: String, key: String, file: File): MyUploadImpl {
        return this.upload(PutObjectRequest(bucketName, key, file))
    }

    @Throws(AmazonServiceException::class, AmazonClientException::class)
    override fun upload(putObjectRequest: PutObjectRequest): MyUploadImpl {
        return this.doUpload(
            putObjectRequest,
            null as TransferStateChangeListener?,
            null as S3ProgressListener?,
            null as PersistableUpload?
        )
    }

    @Throws(AmazonServiceException::class, AmazonClientException::class)
    override fun upload(putObjectRequest: PutObjectRequest, progressListener: S3ProgressListener): MyUploadImpl {
        return this.doUpload(
            putObjectRequest,
            null as TransferStateChangeListener?,
            progressListener,
            null as PersistableUpload?
        )
    }

    @Throws(AmazonServiceException::class, AmazonClientException::class)
    private fun doUpload(
        putObjectRequest: PutObjectRequest,
        stateListener: TransferStateChangeListener?,
        progressListener: S3ProgressListener?,
        persistableUpload: PersistableUpload?
    ): MyUploadImpl {
        appendSingleObjectUserAgent(putObjectRequest)
        val multipartUploadId = persistableUpload?.multipartUploadId
        if (putObjectRequest.metadata == null) {
            putObjectRequest.metadata = ObjectMetadata()
        }

        val metadata = putObjectRequest.metadata
        val file = TransferManagerUtils.getRequestFile(putObjectRequest)
        if (file != null) {
            metadata.contentLength = file.length()
            if (metadata.contentType == null) {
                metadata.contentType = Mimetypes.getInstance().getMimetype(file)
            }
        } else require(multipartUploadId == null) { "Unable to resume the upload. No file specified." }

        val description = "Uploading to " + putObjectRequest.bucketName + "/" + putObjectRequest.key
        val transferProgress = TransferProgress()
        transferProgress.totalBytesToTransfer = TransferManagerUtils.getContentLength(putObjectRequest)
        val listenerChain = S3ProgressListenerChain(
            *arrayOf(
                TransferProgressUpdatingListener(transferProgress),
                putObjectRequest.generalProgressListener,
                progressListener
            )
        )
        putObjectRequest.generalProgressListener = listenerChain
        val upload = MyUploadImpl(putObjectRequest, description, transferProgress, listenerChain, stateListener)
        val uploadCallable = UploadCallable(
            this,
            this.executorService,
            upload,
            putObjectRequest,
            listenerChain,
            multipartUploadId,
            transferProgress
        )
        val watcher =
            UploadMonitor.create(this, upload, this.executorService, uploadCallable, putObjectRequest, listenerChain)
        upload.monitor = watcher
        return upload
    }

    override fun download(bucket: String, key: String, file: File): Download {
        return this.download(bucket, key, file, 0L)
    }

    override fun download(bucket: String, key: String, file: File, timeoutMillis: Long): Download {
        return this.download(GetObjectRequest(bucket, key), file, timeoutMillis)
    }

    override fun download(getObjectRequest: GetObjectRequest, file: File): Download {
        return this.download(getObjectRequest, file, 0L)
    }

    override fun download(getObjectRequest: GetObjectRequest, file: File, timeoutMillis: Long): Download {
        return this.doDownload(
            getObjectRequest,
            file,
            null as TransferStateChangeListener?,
            null as S3ProgressListener?,
            false,
            timeoutMillis,
            null as Int?,
            0L
        )
    }

    override fun download(
        getObjectRequest: GetObjectRequest,
        file: File,
        progressListener: S3ProgressListener
    ): Download {
        return this.doDownload(
            getObjectRequest,
            file,
            null as TransferStateChangeListener?,
            progressListener,
            false,
            0L,
            null as Int?,
            0L
        )
    }

    override fun download(
        getObjectRequest: GetObjectRequest,
        file: File,
        progressListener: S3ProgressListener,
        timeoutMillis: Long
    ): Download {
        return this.doDownload(
            getObjectRequest,
            file,
            null as TransferStateChangeListener?,
            progressListener,
            false,
            timeoutMillis,
            null as Int?,
            0L
        )
    }

    override fun download(
        getObjectRequest: GetObjectRequest,
        file: File,
        progressListener: S3ProgressListener,
        timeoutMillis: Long,
        resumeOnRetry: Boolean
    ): Download {
        return this.doDownload(
            getObjectRequest,
            file,
            null as TransferStateChangeListener?,
            progressListener,
            false,
            timeoutMillis,
            null as Int?,
            0L,
            resumeOnRetry
        )
    }

    private fun doDownload(
        getObjectRequest: GetObjectRequest,
        file: File,
        stateListener: TransferStateChangeListener?,
        s3progressListener: S3ProgressListener?,
        resumeExistingDownload: Boolean,
        timeoutMillis: Long,
        lastFullyDownloadedPart: Int?,
        lastModifiedTimeRecordedDuringPause: Long,
        resumeOnRetry: Boolean = false
    ): Download {
        this.assertParameterNotNull(getObjectRequest, "A valid GetObjectRequest must be provided to initiate download")
        this.assertParameterNotNull(file, "A valid file must be provided to download into")
        appendSingleObjectUserAgent(getObjectRequest)
        val description = "Downloading from " + getObjectRequest.bucketName + "/" + getObjectRequest.key
        val transferProgress = TransferProgress()
        val listenerChain = S3ProgressListenerChain(
            *arrayOf(
                TransferProgressUpdatingListener(transferProgress),
                getObjectRequest.generalProgressListener,
                s3progressListener
            )
        )
        getObjectRequest.generalProgressListener = ProgressListenerChain(
            TransferCompletionFilter(),
            *arrayOf<ProgressListener>(listenerChain)
        )
        val getObjectMetadataRequest = RequestCopyUtils.createGetObjectMetadataRequestFrom(getObjectRequest)
        val objectMetadata = this.s3.getObjectMetadata(getObjectMetadataRequest)
        val lastModifiedTime = objectMetadata.lastModified.time
        var startingByte = 0L
        val range = getObjectRequest.range
        val lastByte: Long
        if (range != null && range.size == 2) {
            startingByte = range[0]
            lastByte = range[1]
        } else {
            lastByte = objectMetadata.contentLength - 1L
        }

        val origStartingByte = startingByte
        val isDownloadParallel =
            !this.configuration.isDisableParallelDownloads && TransferManagerUtils.isDownloadParallelizable(
                this.s3, getObjectRequest, ServiceUtils.getPartCount(getObjectRequest, this.s3)
            )
        val download = DownloadImpl(
            description,
            transferProgress,
            listenerChain,
            null as S3Object?,
            stateListener,
            getObjectRequest,
            file,
            objectMetadata,
            isDownloadParallel
        )
        var totalBytesToDownload = lastByte - startingByte + 1L
        transferProgress.totalBytesToTransfer = totalBytesToDownload
        if (totalBytesToDownload > 0L && !isDownloadParallel) {
            getObjectRequest.withRange(startingByte, lastByte)
        }

        var fileLength = -1L
        if (resumeExistingDownload) {
            if (this.isS3ObjectModifiedSincePause(lastModifiedTime, lastModifiedTimeRecordedDuringPause)) {
                throw AmazonClientException("The requested object in bucket " + getObjectRequest.bucketName + " with key " + getObjectRequest.key + " is modified on Amazon S3 since the last pause.")
            }

            getObjectRequest.unmodifiedSinceConstraint = Date(lastModifiedTime)
            if (!isDownloadParallel) {
                if (!FileLocks.lock(file)) {
                    throw FileLockException("Fail to lock $file for resume download")
                }

                try {
                    if (file.exists()) {
                        fileLength = file.length()
                        startingByte += fileLength
                        getObjectRequest.setRange(startingByte, lastByte)
                        transferProgress.updateProgress(
                            min(fileLength.toDouble(), totalBytesToDownload.toDouble()).toLong()
                        )
                        totalBytesToDownload = lastByte - startingByte + 1L
                        if (log.isDebugEnabled) {
                            log.debug("Resume download: totalBytesToDownload=$totalBytesToDownload, origStartingByte=$origStartingByte, startingByte=$startingByte, lastByte=$lastByte, numberOfBytesRead=$fileLength, file: $file")
                        }
                    }
                } finally {
                    FileLocks.unlock(file)
                }
            }
        }

        require(totalBytesToDownload >= 0L) { "Unable to determine the range for download operation." }
        val latch = CountDownLatch(1)
        val future = this.executorService.submit(
            DownloadCallable(
                this.s3,
                latch,
                getObjectRequest,
                resumeExistingDownload,
                download,
                file,
                startingByte,
                fileLength,
                timeoutMillis,
                this.timedThreadPool,
                this.executorService,
                lastFullyDownloadedPart,
                isDownloadParallel,
                resumeOnRetry
            )
        )
        download.monitor = DownloadMonitor(download, future)
        latch.countDown()
        return download
    }

    private fun isS3ObjectModifiedSincePause(
        lastModifiedTimeRecordedDuringResume: Long,
        lastModifiedTimeRecordedDuringPause: Long
    ): Boolean {
        return lastModifiedTimeRecordedDuringResume != lastModifiedTimeRecordedDuringPause
    }

    override fun downloadDirectory(
        bucketName: String,
        keyPrefix: String,
        destinationDirectory: File
    ): MultipleFileDownload {
        return this.downloadDirectory(bucketName, keyPrefix, destinationDirectory, false)
    }

    override fun downloadDirectory(
        bucketName: String,
        keyPrefix: String,
        destinationDirectory: File,
        filter: KeyFilter
    ): MultipleFileDownload {
        return this.downloadDirectory(bucketName, keyPrefix, destinationDirectory, false, filter)
    }

    override fun downloadDirectory(
        bucketName: String,
        keyPrefix: String,
        destinationDirectory: File,
        resumeOnRetry: Boolean
    ): MultipleFileDownload {
        return this.downloadDirectory(
            bucketName,
            keyPrefix,
            destinationDirectory,
            resumeOnRetry,
            (null as KeyFilter?)!!
        )
    }

    override fun downloadDirectory(
        bucketName: String,
        keyPrefix: String,
        destinationDirectory: File,
        resumeOnRetry: Boolean,
        filter: KeyFilter
    ): MultipleFileDownload {
        var keyPrefix: String? = keyPrefix
        var filter: KeyFilter? = filter
        if (keyPrefix == null) {
            keyPrefix = ""
        }

        if (filter == null) {
            filter = KeyFilter.INCLUDE_ALL
        }

        val objectSummaries = mutableListOf<S3ObjectSummary>()
        val commonPrefixes = Stack<String>()
        commonPrefixes.add(keyPrefix)
        var totalSize = 0L

        do {
            val prefix = commonPrefixes.pop()
            var listObjectsResponse: ObjectListing? = null

            do {
                if (listObjectsResponse == null) {
                    val listObjectsRequest =
                        ListObjectsRequest().withBucketName(bucketName).withDelimiter("/").withPrefix(prefix)
                    listObjectsResponse = this.s3.listObjects(listObjectsRequest)
                } else {
                    listObjectsResponse = this.s3.listNextBatchOfObjects(listObjectsResponse)
                }

                val var24: Iterator<*> = listObjectsResponse.objectSummaries.iterator()

                while (true) {
                    while (var24.hasNext()) {
                        val s = var24.next() as S3ObjectSummary
                        if (!filter!!.shouldInclude(s)) {
                            log.debug("Skipping " + s.key + " as it does not match filter.")
                        } else {
                            if (this.leavesRoot(destinationDirectory, s.key)) {
                                throw RuntimeException("Cannot download key " + s.key + ", its relative path resolves outside the parent directory.")
                            }

                            if (s.key != prefix && !listObjectsResponse.commonPrefixes.contains(s.key + "/")) {
                                objectSummaries.add(s)
                                totalSize += s.size
                            } else {
                                log.debug("Skipping download for object " + s.key + " since it is also a virtual directory")
                            }
                        }
                    }

                    commonPrefixes.addAll(listObjectsResponse.commonPrefixes)
                    break
                }
            } while (listObjectsResponse!!.isTruncated)
        } while (!commonPrefixes.isEmpty())

        val additionalListeners = ProgressListenerChain(*arrayOfNulls(0))
        val transferProgress = TransferProgress()
        transferProgress.totalBytesToTransfer = totalSize
        val listener: ProgressListener =
            MultipleFileTransferProgressUpdatingListener(transferProgress, additionalListeners)
        val downloads = mutableListOf<DownloadImpl>()
        val description = "Downloading from $bucketName/$keyPrefix"
        val multipleFileDownload = MultipleFileDownloadImpl(
            description,
            transferProgress,
            additionalListeners,
            keyPrefix,
            bucketName,
            downloads
        )
        multipleFileDownload.monitor = MultipleFileTransferMonitor(multipleFileDownload, downloads)
        val latch = CountDownLatch(1)
        val transferListener = MultipleFileTransferStateChangeListener(latch, multipleFileDownload)
        val var18: Iterator<*> = objectSummaries.iterator()

        while (var18.hasNext()) {
            val summary = var18.next() as S3ObjectSummary
            val f = File(destinationDirectory, summary.key)
            val parentFile = f.parentFile
            if (!parentFile.exists() && !parentFile.mkdirs()) {
                throw RuntimeException("Couldn't create parent directories for " + f.absolutePath)
            }

            val req =
                GetObjectRequest(summary.bucketName, summary.key).withGeneralProgressListener<AmazonWebServiceRequest>(
                    listener
                ) as GetObjectRequest
            downloads.add(
                this.doDownload(
                    req,
                    f,
                    transferListener,
                    null as S3ProgressListener?,
                    false,
                    0L,
                    null as Int?,
                    0L,
                    resumeOnRetry
                ) as DownloadImpl
            )
        }

        if (downloads.isEmpty()) {
            multipleFileDownload.state = TransferState.Completed
            return multipleFileDownload
        } else {
            latch.countDown()
            return multipleFileDownload
        }
    }

    private fun leavesRoot(localBaseDirectory: File, key: String): Boolean {
        try {
            return !File(localBaseDirectory, key).canonicalPath.startsWith(localBaseDirectory.canonicalPath)
        } catch (var4: IOException) {
            throw RuntimeException("Unable to canonicalize paths", var4)
        }
    }

    override fun uploadDirectory(
        bucketName: String,
        virtualDirectoryKeyPrefix: String,
        directory: File,
        includeSubdirectories: Boolean
    ): MultipleFileUpload {
        return this.uploadDirectory(
            bucketName,
            virtualDirectoryKeyPrefix,
            directory,
            includeSubdirectories,
            (null as ObjectMetadataProvider?)!!
        )
    }

    override fun uploadDirectory(
        bucketName: String,
        virtualDirectoryKeyPrefix: String,
        directory: File,
        includeSubdirectories: Boolean,
        metadataProvider: ObjectMetadataProvider
    ): MultipleFileUpload {
        return this.uploadDirectory(
            bucketName,
            virtualDirectoryKeyPrefix,
            directory,
            includeSubdirectories,
            metadataProvider,
            (null as ObjectTaggingProvider?)!!
        )
    }

    override fun uploadDirectory(
        bucketName: String,
        virtualDirectoryKeyPrefix: String,
        directory: File,
        includeSubdirectories: Boolean,
        metadataProvider: ObjectMetadataProvider,
        taggingProvider: ObjectTaggingProvider
    ): MultipleFileUpload {
        if (directory != null && directory.exists() && directory.isDirectory) {
            val files = mutableListOf<File>()
            this.listFiles(directory, files, includeSubdirectories)
            return this.uploadFileList(
                bucketName,
                virtualDirectoryKeyPrefix,
                directory,
                files,
                metadataProvider,
                taggingProvider
            )
        } else {
            throw IllegalArgumentException("Must provide a directory to upload")
        }
    }

    override fun uploadFileList(
        bucketName: String,
        virtualDirectoryKeyPrefix: String,
        directory: File,
        files: List<File>
    ): MultipleFileUpload {
        return this.uploadFileList(
            bucketName,
            virtualDirectoryKeyPrefix,
            directory,
            files,
            null as ObjectMetadataProvider?
        )
    }

    //    public MultipleFileUpload uploadFileList(String bucketName, String virtualDirectoryKeyPrefix, File directory, List<File> files, ObjectMetadataProvider metadataProvider) {
    //        return this.uploadFileList(bucketName, virtualDirectoryKeyPrefix, directory, files, metadataProvider, (ObjectTaggingProvider)null, null);
    //    }
    fun uploadFileList(
        bucketName: String?,
        virtualDirectoryKeyPrefix: String?,
        directory: File?,
        files: List<File?>?,
        metadataProvider: ObjectMetadataProvider?,
        cannedAccessControlList: CannedAccessControlList?
    ): MultipleFileUpload {
        return this.uploadFileList(
            bucketName,
            virtualDirectoryKeyPrefix,
            directory,
            files,
            metadataProvider,
            null as ObjectTaggingProvider?,
            cannedAccessControlList
        )
    }

    fun uploadFileList(
        bucketName: String?,
        virtualDirectoryKeyPrefix: String?,
        directory: File?,
        files: List<File?>?,
        metadataProvider: ObjectMetadataProvider?,
        taggingProvider: ObjectTaggingProvider?,
        cannedAccessControlList: CannedAccessControlList?
    ): MultipleFileUpload {
        var virtualDirectoryKeyPrefix = virtualDirectoryKeyPrefix
        if (directory != null && directory.exists() && directory.isDirectory) {
            if (virtualDirectoryKeyPrefix != null && virtualDirectoryKeyPrefix.length != 0) {
                if (!virtualDirectoryKeyPrefix.endsWith("/")) {
                    virtualDirectoryKeyPrefix = "$virtualDirectoryKeyPrefix/"
                }
            } else {
                virtualDirectoryKeyPrefix = ""
            }

            val additionalListeners = ProgressListenerChain(*arrayOfNulls(0))
            val progress = TransferProgress()
            val listener: ProgressListener = MultipleFileTransferProgressUpdatingListener(progress, additionalListeners)
            val uploads = mutableListOf<UploadImpl>()
            val multipleFileUpload = MultipleFileUploadImpl(
                "Uploading etc",
                progress,
                additionalListeners,
                virtualDirectoryKeyPrefix,
                bucketName,
                uploads
            )
            multipleFileUpload.monitor = MultipleFileTransferMonitor(multipleFileUpload, uploads)
            val latch = CountDownLatch(1)
            val transferListener = MultipleFileTransferStateChangeListener(latch, multipleFileUpload)
            if (files != null && !files.isEmpty()) {
                var startingPosition = directory.absolutePath.length
                if (!directory.absolutePath.endsWith(File.separator)) {
                    ++startingPosition
                }

                var totalSize = 0L
                val var17: Iterator<*> = files.iterator()

                while (var17.hasNext()) {
                    val f = var17.next() as File
                    if (f.isFile) {
                        totalSize += f.length()
                        val key = f.absolutePath.substring(startingPosition).replace("\\\\".toRegex(), "/")
                        val uploadContext = UploadContext(f, bucketName, key)
                        val metadata = ObjectMetadata()
                        var objectTagging: ObjectTagging? = null
                        metadataProvider?.provideObjectMetadata(f, metadata)

                        if (taggingProvider != null) {
                            objectTagging = taggingProvider.provideObjectTags(uploadContext)
                        }
                        uploads.add(
                            this.doUpload(
                                PutObjectRequest(
                                    bucketName,
                                    virtualDirectoryKeyPrefix + key, f
                                ).withMetadata(metadata)
                                    .withTagging(objectTagging)
                                    .withCannedAcl(cannedAccessControlList)
                                    .withGeneralProgressListener(listener),
                                transferListener,
                                null as S3ProgressListener?,
                                null as PersistableUpload?
                            )
                        )
                    }
                }

                progress.totalBytesToTransfer = totalSize
            } else {
                multipleFileUpload.state = TransferState.Completed
            }

            latch.countDown()
            return multipleFileUpload
        } else {
            throw IllegalArgumentException("Must provide a common base directory for uploaded files")
        }
    }

    private fun listFiles(dir: File, results: MutableList<File>, includeSubDirectories: Boolean) {
        val found = dir.listFiles()
        if (found != null) {
            val var5 = found
            val var6 = found.size

            for (var7 in 0 until var6) {
                val f = var5[var7]
                if (f.isDirectory) {
                    if (includeSubDirectories) {
                        this.listFiles(f, results, includeSubDirectories)
                    }
                } else {
                    results.add(f)
                }
            }
        }
    }

    @Throws(AmazonServiceException::class, AmazonClientException::class)
    override fun abortMultipartUploads(bucketName: String, date: Date) {
        var uploadListing =
            this.s3.listMultipartUploads(appendSingleObjectUserAgent(ListMultipartUploadsRequest(bucketName)))

        do {
            val var4: Iterator<*> = uploadListing.multipartUploads.iterator()

            while (var4.hasNext()) {
                val upload = var4.next() as MultipartUpload
                if (upload.initiated.compareTo(date) < 0) {
                    this.s3.abortMultipartUpload(
                        appendSingleObjectUserAgent(
                            AbortMultipartUploadRequest(
                                bucketName,
                                upload.key,
                                upload.uploadId
                            )
                        )
                    )
                }
            }

            val request =
                ListMultipartUploadsRequest(bucketName).withUploadIdMarker(uploadListing.nextUploadIdMarker)
                    .withKeyMarker(uploadListing.nextKeyMarker)
            uploadListing =
                this.s3.listMultipartUploads(appendSingleObjectUserAgent(request) as ListMultipartUploadsRequest)
        } while (uploadListing.isTruncated)
    }

    override fun shutdownNow() {
        this.shutdownNow(true)
    }

    override fun shutdownNow(shutDownS3Client: Boolean) {
        if (this.shutDownThreadPools) {
            this.executorService.shutdownNow()
            this.timedThreadPool.shutdownNow()
        }

        if (shutDownS3Client) {
            this.s3.shutdown()
        }
    }

    private fun shutdownThreadPools() {
        if (this.shutDownThreadPools) {
            this.executorService.shutdown()
            this.timedThreadPool.shutdown()
        }
    }

    @Throws(AmazonServiceException::class, AmazonClientException::class)
    override fun copy(
        sourceBucketName: String,
        sourceKey: String,
        destinationBucketName: String,
        destinationKey: String
    ): Copy {
        return this.copy(CopyObjectRequest(sourceBucketName, sourceKey, destinationBucketName, destinationKey))
    }

    override fun copy(copyObjectRequest: CopyObjectRequest): Copy {
        return this.copy(copyObjectRequest, (null as TransferStateChangeListener?)!!)
    }

    @Throws(AmazonServiceException::class, AmazonClientException::class)
    override fun copy(copyObjectRequest: CopyObjectRequest, stateChangeListener: TransferStateChangeListener): Copy {
        return this.copy(copyObjectRequest, this.s3, stateChangeListener)
    }

    @Throws(AmazonServiceException::class, AmazonClientException::class)
    override fun copy(
        copyObjectRequest: CopyObjectRequest,
        srcS3: AmazonS3,
        stateChangeListener: TransferStateChangeListener
    ): Copy {
        appendSingleObjectUserAgent(copyObjectRequest)
        this.assertParameterNotNull(
            copyObjectRequest.sourceBucketName,
            "The source bucket name must be specified when a copy request is initiated."
        )
        this.assertParameterNotNull(
            copyObjectRequest.sourceKey,
            "The source object key must be specified when a copy request is initiated."
        )
        this.assertParameterNotNull(
            copyObjectRequest.destinationBucketName,
            "The destination bucket name must be specified when a copy request is initiated."
        )
        this.assertParameterNotNull(
            copyObjectRequest.destinationKey,
            "The destination object key must be specified when a copy request is initiated."
        )
        this.assertParameterNotNull(srcS3, "The srcS3 parameter is mandatory")
        val description =
            "Copying object from " + copyObjectRequest.sourceBucketName + "/" + copyObjectRequest.sourceKey + " to " + copyObjectRequest.destinationBucketName + "/" + copyObjectRequest.destinationKey
        val getObjectMetadataRequest =
            GetObjectMetadataRequest(
                copyObjectRequest.sourceBucketName,
                copyObjectRequest.sourceKey
            ).withSSECustomerKey(copyObjectRequest.sourceSSECustomerKey)
                .withRequesterPays(copyObjectRequest.isRequesterPays).withVersionId(copyObjectRequest.sourceVersionId)
        val metadata = srcS3.getObjectMetadata(getObjectMetadataRequest)
        val transferProgress = TransferProgress()
        transferProgress.totalBytesToTransfer = metadata.contentLength
        val listenerChain =
            ProgressListenerChain(*arrayOf<ProgressListener>(TransferProgressUpdatingListener(transferProgress)))
        val copy = CopyImpl(description, transferProgress, listenerChain, stateChangeListener)
        val copyCallable = CopyCallable(this, this.executorService, copy, copyObjectRequest, metadata, listenerChain)
        val watcher =
            CopyMonitor.create(this, copy, this.executorService, copyCallable, copyObjectRequest, listenerChain)
        copy.monitor = watcher
        return copy
    }

    override fun resumeUpload(persistableUpload: PersistableUpload): Upload {
        this.assertParameterNotNull(persistableUpload, "PauseUpload is mandatory to resume a upload.")
        this.configuration.minimumUploadPartSize = persistableUpload.partSize
        this.configuration.multipartUploadThreshold = persistableUpload.mutlipartUploadThreshold
        return this.doUpload(
            PutObjectRequest(
                persistableUpload.bucketName,
                persistableUpload.key,
                File(persistableUpload.file)
            ), null as TransferStateChangeListener?, null as S3ProgressListener?, persistableUpload
        )
    }

    override fun resumeDownload(persistableDownload: PersistableDownload): Download {
        this.assertParameterNotNull(persistableDownload, "PausedDownload is mandatory to resume a download.")
        val request =
            GetObjectRequest(persistableDownload.bucketName, persistableDownload.key, persistableDownload.versionId)
        if (persistableDownload.range != null && persistableDownload.range.size == 2) {
            val range = persistableDownload.range
            request.setRange(range[0], range[1])
        }

        request.isRequesterPays = persistableDownload.isRequesterPays
        request.responseHeaders = persistableDownload.responseHeaders
        return this.doDownload(
            request,
            File(persistableDownload.file),
            null as TransferStateChangeListener?,
            null as S3ProgressListener?,
            true,
            0L,
            persistableDownload.lastFullyDownloadedPartNumber,
            persistableDownload.getlastModifiedTime()
        )
    }

    private fun assertParameterNotNull(parameterValue: Any?, errorMessage: String) {
        requireNotNull(parameterValue) { errorMessage }
    }

    @Throws(Throwable::class)
    override fun finalize() {
        this.shutdownThreadPools()
    }

    private fun checkMutability() {
        if (this.isImmutable) {
            throw UnsupportedOperationException("TransferManager is immutable when created with the builder.")
        }
    }

    companion object {
        private val log: Logger = LoggerFactory.getLogger(TransferManager::class.java)
        private val USER_AGENT = TransferManager::class.java.name + "/" + VersionInfoUtils.getVersion()
        private val USER_AGENT_MULTIPART =
            TransferManager::class.java.name + "_multipart/" + VersionInfoUtils.getVersion()
        private const val DEFAULT_DELIMITER = "/"
        private val daemonThreadFactory: ThreadFactory = object : ThreadFactory {
            val threadCount: AtomicInteger = AtomicInteger(0)

            override fun newThread(r: Runnable): Thread {
                val threadNumber = threadCount.incrementAndGet()
                val thread = Thread(r)
                thread.isDaemon = true
                thread.name = "S3TransferManagerTimedThread-$threadNumber"
                return thread
            }
        }

        fun <X : AmazonWebServiceRequest?> appendSingleObjectUserAgent(request: X): X {
            request!!.requestClientOptions.appendUserAgent(USER_AGENT)
            return request
        }

        fun <X : AmazonWebServiceRequest?> appendMultipartUserAgent(request: X): X {
            request!!.requestClientOptions.appendUserAgent(USER_AGENT_MULTIPART)
            return request
        }
    }
}
